/*
 * Copyright 2009-2012 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License i distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.paoding.rose.jade.dataaccess;

import net.paoding.rose.jade.properties.JadeDatasourceProperties;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.RowMapperResultSetExtractor;
import org.springframework.jdbc.core.SqlParameterValue;
import org.springframework.jdbc.core.SqlTypeValue;
import org.springframework.jdbc.core.StatementCreatorUtils;
import org.springframework.jdbc.support.KeyHolder;

import javax.sql.DataSource;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;

/**
 * @author 王志亮 [qieqie.wang@gmail.com]
 * @author 廖涵 [in355hz@gmail.com]
 */
public class DataAccessImpl implements DataAccess {

    private final JdbcTemplate jdbcTemplate;

    DataAccessImpl(JdbcTemplate jdbcTemplate, JadeDatasourceProperties jadeDatasourceProperties) {
        this.jdbcTemplate = jdbcTemplate;
        if (jadeDatasourceProperties.getFetchSize() != -1) jdbcTemplate.setFetchSize(jadeDatasourceProperties.getFetchSize());
        if (jadeDatasourceProperties.getMaxRows() != -1) jdbcTemplate.setMaxRows(jadeDatasourceProperties.getMaxRows());
        if (jadeDatasourceProperties.getQueryTimeout() != -1) jdbcTemplate.setQueryTimeout(jadeDatasourceProperties.getQueryTimeout());
    }

    // ------------------------------------------------

    @Override
    public DataSource getDataSource() {
        return this.jdbcTemplate.getDataSource();
    }

    @Override
    public <T> List<T> select(String sql, List<Object> args, RowMapper<T> rowMapper) {
        PreparedStatementCreator csc = getPreparedStatementCreator(sql, args, false, false);
        return jdbcTemplate.query(csc, new RowMapperResultSetExtractor<T>(rowMapper));
    }

    @Override
    public <T> Stream<T> selectForStream(String sql, List<Object> args, RowMapper<T> rowMapper) {
        PreparedStatementCreator csc = getPreparedStatementCreator(sql, args, false, true);
        return jdbcTemplate.queryForStream(csc, rowMapper);
    }

    @Override
    public int update(String sql, List<Object> args, KeyHolder generatedKeyHolder) {
        boolean returnKeys = generatedKeyHolder != null;
        PreparedStatementCreator psc = getPreparedStatementCreator(sql, args, returnKeys, false);
        if (generatedKeyHolder == null) {
            return jdbcTemplate.update(psc);
        } else {
            return jdbcTemplate.update(psc, generatedKeyHolder);
        }
    }

    @Override
    public int[] batchUpdate(String sql, List<Object[]> batchArgs) {
        return jdbcTemplate.batchUpdate(sql, batchArgs);
    }

    private PreparedStatementCreator getPreparedStatementCreator(String sql, List<Object> args, boolean returnKeys, boolean returnStream) {
        return con -> {
            PreparedStatement ps;
            if (returnKeys) {
                ps = con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
            } else if (returnStream) {
                ps = con.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
            } else {
                ps = con.prepareStatement(sql);
            }

            if (args != null) {
                for (int i = 0; i < args.size(); i++) {
                    Object arg = args.get(i);
                    if (arg instanceof SqlParameterValue) {
                        SqlParameterValue paramValue = (SqlParameterValue) arg;
                        StatementCreatorUtils.setParameterValue(ps, i + 1, paramValue, paramValue.getValue());
                    } else {
                        int sqlType = Objects.isNull(arg)
                                ? SqlTypeValue.TYPE_UNKNOWN : StatementCreatorUtils.javaTypeToSqlParameterType(arg.getClass());
                        StatementCreatorUtils.setParameterValue(ps, i + 1, sqlType, arg);
                    }
                }
            }
            return ps;
        };
    }

}
